#include "webhost.h"
#include "webwindow.h"
#include "webpage.h"
#include "webwindow.h"
#include "webdialog.h"
#include "qwebframe.h"
#include "qnetworkrequest.h"
#include "qnetworkreply.h"
#include "qwebsecurityorigin.h"
#include "qtimer.h"
#include "qdebug.h"

QHash<QString, QObject*> WebHost::JSOTempVariable;
QHash<QString, QVariant> WebHost::JSVTempVariable;
QEventLoop* WebHost::eventLoop = 0;
QFileDialog* WebHost::fileDialog = 0;

QString WebHost::setJSTempVariable(QObject *value) {
	QString key = ("&" + QString::number(qrand()));
	WebHost::JSOTempVariable[key] = value;
	return key;
}

QString WebHost::setJSTempVariable(QVariant value) {
	QString key = QString::number(qrand());
	WebHost::JSVTempVariable[key] = value;
	return key;
}

bool WebHost::fireJSEvent(QWebFrame* sourceFrame, QString eventType, QString eventDataRef) {
	QStringList framePath;
	if (sourceFrame == 0) sourceFrame = this->webPage->mainFrame();
	while (sourceFrame->parentFrame()) {
		framePath.append(QString::number(sourceFrame->parentFrame()->childFrames().indexOf(sourceFrame)));
		sourceFrame = sourceFrame->parentFrame();
	}

	QObject* targetWindow = this;
	while (targetWindow->parent()) {
		if (!targetWindow->parent()->parent()) break;
		targetWindow = targetWindow->parent();
	}
	WebHost* topHost = (WebHost*)targetWindow;

	QString hostRef = this->setJSTempVariable(sourceFrame->page()->parent());
	topHost->webPage->mainFrame()->addToJavaScriptWindowObject("", this);
	return topHost->webPage->mainFrame()->evaluateJavaScript("(function() {"
		"delete window[''];"

		"var eventDataRef = '" + eventDataRef + "';"
		"var eventData = (eventDataRef ? (eventDataRef[0] == '&' ?"
			"this.getJSOTempVariable(eventDataRef) :"
			"this.getJSVTempVariable(eventDataRef)"
		") : null); "

		"var event = document.createEvent('UIEvents');"
		"event.host = this.getJSOTempVariable('"+hostRef+"');"
		"event.frame = ["+framePath.join(",")+"];"
		"event.data = eventData;"

		"event.initEvent('" + eventType + "', false, true);"
		"dispatchEvent(event);"
		"if (!event.returnValue) return false;"
		"return true;"

		/*
		"var event = document.createEvent('UIEvents');"
		"event.host = this.getJSOTempVariable('"+hostRef+"');"
		"event.initEvent('cleanup', false, true);"
		"dispatchEvent(event);"
		*/
	"}).call(window['']);").toBool();
}

QVariantMap WebHost::dialog(QFileDialog::AcceptMode acceptMode, QVariantMap options) {
	// initialize result
	QVariantMap dialogResult;
	dialogResult["error"] = true;
	// check if we already have dialog opened
	if (WebHost::fileDialog or WebHost::eventLoop) return dialogResult;
	// initialize options
	bool dirOnly = ((QVariant)options["dironly"]).toBool();
	QString windowTitle = ((QVariant)options["title"]).toString();
	QString initialDir = ((QVariant)options["directory"]).toString();
	QString fileName = ((QVariant)options["filename"]).toString();
	bool multiSelect = ((QVariant)options["multi-select"]).toBool();
	bool forceOverwrite = ((QVariant)options["force-overwrite"]).toBool();
	QStringList filters = ((QVariant)options["filters"]).toStringList();
	// create dialog
	WebHost::fileDialog = new QFileDialog(this->getParentWindow());
	QObject::connect(WebHost::fileDialog, SIGNAL(accepted()), this, SLOT(fdAccepted()));
	QObject::connect(WebHost::fileDialog, SIGNAL(rejected()), this, SLOT(fdRejected()));
	// set generic options
	WebHost::fileDialog->setAcceptMode(acceptMode);
	if (!windowTitle.isEmpty()) WebHost::fileDialog->setWindowTitle(windowTitle);
	if (!initialDir.isEmpty()) WebHost::fileDialog->setDirectory(initialDir);
	if (!fileName.isEmpty()) WebHost::fileDialog->selectFile(fileName);
	// set type specific options
	if (acceptMode == QFileDialog::AcceptOpen) {
		if (!dirOnly) {
			if (!filters.isEmpty()) WebHost::fileDialog->setFilters(filters);
			if (multiSelect) WebHost::fileDialog->setFileMode(QFileDialog::ExistingFiles);
			else WebHost::fileDialog->setFileMode(QFileDialog::ExistingFile);
		} else WebHost::fileDialog->setFileMode(QFileDialog::DirectoryOnly);
	} else WebHost::fileDialog->setOption(QFileDialog::DontConfirmOverwrite, forceOverwrite);
	// execute dialog
	if (WebHost::fileDialog->parent()) {
		WebHost::eventLoop = new QEventLoop();
		WebHost::fileDialog->open();
		WebHost::eventLoop->exec();
		delete WebHost::eventLoop;
		WebHost::eventLoop = 0;
	} else WebHost::fileDialog->exec();
	// disconnect signals
	QObject::disconnect(this, SLOT(fdAccepted()));
	QObject::disconnect(this, SLOT(fdRejected()));
	// gather result
	bool accepted = WebHost::fileDialog->property("accepted").toBool();
	QStringList files;
	if (accepted) {
		files = WebHost::fileDialog->selectedFiles();
		int c = files.length();
		while (c--) files[c].prepend("file://");
	}
	// destroy dialog
	delete WebHost::fileDialog;
	WebHost::fileDialog = 0;
	// initialize and return result
	dialogResult["error"] = false;
	dialogResult["accepted"] = accepted;
	dialogResult["files"] = files;
	return dialogResult;
}

void WebHost::fdAccepted() {
	WebHost::fileDialog->setProperty("accepted", true);
	if (!WebHost::eventLoop) return;
	if (!WebHost::eventLoop->isRunning()) return;
	WebHost::eventLoop->exit();
}

void WebHost::fdRejected() {
	WebHost::fileDialog->setProperty("accepted", false);
	if (!WebHost::eventLoop) return;
	if (!WebHost::eventLoop->isRunning()) return;
	WebHost::eventLoop->exit();
}

bool WebHost::fireEvent(QString eventType, QWebFrame* frame) {
	return this->fireJSEvent(frame, eventType, 0);
}

bool WebHost::fireEvent(QString eventType, QObject *eventData, QWebFrame* frame) {
	return this->fireJSEvent(frame, eventType, this->setJSTempVariable(eventData));
}

bool WebHost::fireEvent(QString eventType, QVariant eventData, QWebFrame* frame) {
	return this->fireJSEvent(frame, eventType, this->setJSTempVariable(eventData));
}

WebPage* WebHost::windowCreate() {
	WebWindow* webWindow = new WebWindow(this);
	WebPage* webPage = webWindow->webPage;
	webPage->setNetworkAccessManager(this->webPage->networkAccessManager());
	webPage->mainFrame()->addToJavaScriptWindowObject("", webWindow);
	return webPage;
}

void WebHost::frameCreated(QWebFrame *frame) {
	QObject::connect(frame, SIGNAL(
		javaScriptWindowObjectCleared()
	), this, SLOT(javaScriptFrameObjectCleared()));
}

void WebHost::javaScriptFrameObjectCleared() {
	QWebFrame* frame = (QWebFrame*)sender();
	this->fireEvent("cleanup", (QWebFrame*)sender());
}

/* public declarations */

WebHost::WebHost(QObject *parent): QObject(parent) {
	this->webPage = new WebPage(this);
	QPalette palette = this->webPage->palette();
	palette.setBrush(QPalette::Base, Qt::transparent);
	this->webPage->setPalette(palette);
	QObject::connect(this->webPage, SIGNAL(
		createWindow()
	), this, SLOT(windowCreate()));
	QObject::connect(webPage, SIGNAL(
		frameCreated(QWebFrame*)
	), this, SLOT(frameCreated(QWebFrame*)));
}

WebHost::~WebHost() {
	if (WebHost::fileDialog) delete WebHost::fileDialog;
	if (WebHost::eventLoop) {
		if (WebHost::eventLoop->isRunning()) {
			WebHost::eventLoop->exit();
		}
		delete WebHost::eventLoop;
	}
	delete this->webPage;
}

QWidget* WebHost::getParentWindow() {
	QWidget* parentView = this->webPage->view();
	if (parentView and parentView->window()) {
		return parentView->window();
	} else return 0;
}

/* exposed declarations */

QObject* WebHost::getJSOTempVariable(QString key) {
	if (!WebHost::JSOTempVariable.contains(key)) return NULL;
	QObject* value = WebHost::JSOTempVariable[key];
	WebHost::JSOTempVariable.remove(key);
	return value;
}

QVariant WebHost::getJSVTempVariable(QString key) {
	if (!WebHost::JSVTempVariable.contains(key)) return 0;
	QVariant value = WebHost::JSVTempVariable[key];
	WebHost::JSVTempVariable.remove(key);
	return value;
}

void WebHost::require(QString filePath) {
	QUrl requestUrl = this->webPage->mainFrame()->url().resolved(filePath);
	QNetworkRequest networkRequest(requestUrl);
	QNetworkReply* networkReply = this->webPage->networkAccessManager()->get(networkRequest);
	this->webPage->mainFrame()->evaluateJavaScript(networkReply->readAll());
	delete networkReply;
}

void WebHost::openDialog(QString url) {
	WebPage* webPage = (new WebDialog(this, url))->webPage;
	webPage->setNetworkAccessManager(this->webPage->networkAccessManager());
}

QVariantMap WebHost::openFolderDialog(QVariantMap options) {
	options["dironly"] = true;
	return this->dialog(QFileDialog::AcceptOpen, options);
}

QVariantMap WebHost::openFileDialog(QVariantMap options) {
	return this->dialog(QFileDialog::AcceptOpen, options);
}

QVariantMap WebHost::saveFileDialog(QVariantMap options) {
	return this->dialog(QFileDialog::AcceptSave, options);
}
